home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / smaltalk / manchest.lha / MANCHESTER / manchester / 4.1 / Forms.st < prev    next >
Text File  |  1993-07-24  |  35KB  |  1,254 lines

  1. "    NAME        Forms
  2.     AUTHOR        Bernard Horan <bernard@cs.man.ac.uk>
  3.     CONTRIBUTOR    Bernard Horan <bernard@cs.man.ac.uk>
  4.     FUNCTION      Pluggable form-filler
  5.     ST-VERSIONS    4.1
  6.     PREREQUISITES     
  7.     CONFLICTS     
  8.     DISTRIBUTION    global
  9.     VERSION        2.0
  10.     DATE        September 1992
  11.     SUMMARY        A changes file containing a pluggable forms model. See the classes for comments and examples. The form offers a choice of different style of interaction: menu, single select, multiple select, text fill, number slider and cycle. The form can be created on a model, which when the form is accepted by the user, is updated to its new state. The form may be opened in either 'dialog' mode (stops all other interaction), or in 'scheduled' mode (the
  12. view has a scheduledController. The user may inetraction with the dialog with
  13. a mouse/pointer but may also quit/reset/acept the form using the keyboard.
  14. This is modelled on a combination of the FormsView and DBox ideas in the
  15. Analyst, but is written completely from scratch.  BH, 25/9/92.
  16.  
  17. I'd like to thank Markus Geltz <Geltz@informatik.uni-stuttgart.dbp.de> for some bug 
  18. fixes and the additional BooleanRow class "!
  19. 'From Objectworks(r)\Smalltalk, Release 4 of 25 October 1990 on 14 November 1991 at 12:47:36 pm'!
  20.  
  21.  
  22. Object subclass: #Form
  23.     instanceVariableNames: 'title rows model isAccepted '
  24.     classVariableNames: ''
  25.     poolDictionaries: ''
  26.     category: 'Forms'!
  27. Form comment:
  28. 'I represent an abstract form. 
  29. Instance variables:
  30. title    <String/Text/ComposedText> The title of the form.
  31. rows    <OrderedCollection> The rows on the form.
  32. model    <Object> Any object can be a model. 
  33. isAccepted    <Boolean> a flag to catch when user input has completed.
  34. Bernard Horan -- 12 November 1991'!
  35.  
  36.  
  37. !Form methodsFor: 'initialize-release'!
  38.  
  39. release
  40.     model removeDependent: self.
  41.     rows do: [:i | i release].
  42.     super release!
  43.  
  44. setModel: aModel
  45.     model := aModel.
  46.     aModel addDependent: self.
  47.     rows := OrderedCollection new.
  48.     isAccepted := false! !
  49.  
  50. !Form methodsFor: 'accessing'!
  51.  
  52. actionTaken
  53.     ^isAccepted!
  54.  
  55. addRow: aFormRow
  56.     rows add: aFormRow.
  57.     aFormRow holder: self!
  58.  
  59. model
  60.     ^model!
  61.  
  62. rows
  63.     ^rows!
  64.  
  65. title
  66.     ^title!
  67.  
  68. title: aStringOrText
  69.     title := aStringOrText! !
  70.  
  71. !Form methodsFor: 'building views'!
  72.  
  73. openDialogView
  74.     "Open a dialogView on myself.
  75.     As it's a dialogView, the view is modal"
  76.     self open.
  77.     FormView openDialogViewOn: self!
  78.  
  79. openScheduledView
  80.     "Open a scheduledView on myself, not modal, but scheduled"
  81.     self open.
  82.     FormView openScheduledViewOn: self! !
  83.  
  84. !Form methodsFor: 'pluggability'!
  85.  
  86. acceptAndClose
  87.     self accept.
  88.     self quit!
  89.  
  90. quit
  91.     isAccepted := true! !
  92.  
  93. !Form methodsFor: 'updating'!
  94.  
  95. update: aSymbol
  96.     "My model has changed, tell my rows to update if necessary"
  97.     rows do: [:row | row update: aSymbol]! !
  98.  
  99. !Form methodsFor: 'private'!
  100.  
  101. accept
  102.     "I have been accepted, tell my rows to accept, ie put their values into
  103.     the model"
  104.     rows do:[:i | i accept]!
  105.  
  106. open
  107.     "Get the values from the model"
  108.     rows do: [:i | i open]!
  109.  
  110. reset
  111.     self open! !
  112. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  113.  
  114. Form class
  115.     instanceVariableNames: ''!
  116.  
  117.  
  118. !Form class methodsFor: 'instance creation'!
  119.  
  120. dialogOn: aModel title: aTitle types: aTypeCollection labels: aLabelCollection getMsgs: aGetCollection updateMsgs: anUpdateCollection defaults: defaultCollection 
  121.     "Create a form on the model, then build and open a dialogView on it.
  122.     Modal"
  123.     | form |
  124.     form := self
  125.                 on: aModel
  126.                 title: aTitle
  127.                 types: aTypeCollection
  128.                 labels: aLabelCollection
  129.                 getMsgs: aGetCollection
  130.                 updateMsgs: anUpdateCollection
  131.                 defaults: defaultCollection.
  132.     form openDialogView.
  133.     ^aModel!
  134.  
  135. model: aModel
  136.     ^self new setModel: aModel!
  137.  
  138. on: aModel title: aTitle types: aTypeCollection labels: aLabelCollection getMsgs: aGetCollection updateMsgs: anUpdateCollection defaults: defaultCollection 
  139.     "Create a form on the model. This method iterates through the 
  140.     collections to add the appropriate rows to the form"
  141.     | form |
  142.     form := self model: aModel.
  143.     form title: aTitle.
  144.     1 to: aTypeCollection size do: [:i | form addRow: (FormRow
  145.                 label: (aLabelCollection at: i)
  146.                 type: (aTypeCollection at: i)
  147.                 getMsg: (aGetCollection at: i)
  148.                 updateMsg: (anUpdateCollection at: i)
  149.                 default: (defaultCollection at: i))].
  150.     ^form!
  151.  
  152. scheduleOn: aModel title: aTitle types: aTypeCollection labels: aLabelCollection getMsgs: aGetCollection updateMsgs: anUpdateCollection defaults: defaultCollection 
  153.     "Create a form on the model, then build and scheduled a window on it.
  154.     Non-modal"
  155.     | form |
  156.     form := self
  157.                 on: aModel
  158.                 title: aTitle
  159.                 types: aTypeCollection
  160.                 labels: aLabelCollection
  161.                 getMsgs: aGetCollection
  162.                 updateMsgs: anUpdateCollection
  163.                 defaults: defaultCollection.
  164.     form openScheduledView! !
  165.  
  166. !Form class methodsFor: 'examples'!
  167.  
  168. example1
  169.     "Form example1"
  170.     "Example showing the use of a dialogView - modal"
  171.     | model |
  172.     model := DummyModel new.
  173.     model obj: #mary; coll: #(jo jim) asOrderedCollection; text: 'The first string'.
  174.     Form
  175.         dialogOn: model
  176.         title: 'Example 1'
  177.         types: #(#select #multiple #fill #number #cycle #menu)
  178.         labels: #('Name' 'Supervisor' 'Comment' 'Age' 'Beast' 'Fish')
  179.         getMsgs: #(#obj #coll #text #num #obj2 #obj3)
  180.         updateMsgs: #(#obj: #coll: #text: #num: #obj2: #obj3:)
  181.         defaults: #(#(#fred #mary #jane ) #(#jo #jill #jim #sam ) 'A piece of string' (1 100 10) (Aardvark Kangaroo Elephant) (Whale Shark Dolphin)).
  182.     ^model!
  183.  
  184. example2
  185.     "Form example2"
  186.     "Example showing the use of a sheduled view - non-modal"
  187.  
  188.     | model |
  189.     model := DummyModel new.
  190.     model obj: #mary; coll: #(#jo #jim ) asOrderedCollection; text: 'The first string'.
  191.     Form
  192.         scheduleOn: model
  193.         title: 'Example 1'
  194.         types: #(#number #select #multiple #fill #cycle #menu )
  195.         labels: #('Age' 'Name' 'Supervisor' 'Comment' 'Beast' 'Fish' )
  196.         getMsgs: #(#num #obj #coll #text #obj2 #obj3 )
  197.         updateMsgs: #(#num: #obj: #coll: #text: #obj2: #obj3: )
  198.         defaults: #(#(1 100 10 ) #(#fred #mary #jane ) #(#jo #jill #jim #sam ) 'A piece of string' #(#Aardvark #Kangaroo #Elephant ) #(#Whale #Shark #Dolphin ) ).
  199.     ^model! !
  200.  
  201. PluggableAdaptor subclass: #LabeledPluggableAdaptor
  202.     instanceVariableNames: 'labelBlock '
  203.     classVariableNames: ''
  204.     poolDictionaries: ''
  205.     category: 'Forms-Support'!
  206. LabeledPluggableAdaptor comment:
  207. 'A refinement of PluggableAdaptor with an extra block to get the label from the
  208. model. The labelBlock i.v. should have the model as its only argument.
  209. Used by LabeledBooleanView class.
  210. Bernard Horan -- 12 November 1991'!
  211.  
  212.  
  213. !LabeledPluggableAdaptor methodsFor: 'initialize-release'!
  214.  
  215. getBlock: aBlock1 putBlock: aBlock2 updateBlock: aBlock3 labelBlock: aBlock4
  216.     self getBlock: aBlock1 putBlock: aBlock2 updateBlock: aBlock3.
  217.     labelBlock := aBlock4! !
  218.  
  219. !LabeledPluggableAdaptor methodsFor: 'accessing'!
  220.  
  221. label
  222.     "Return the label from my model"
  223.     ^ labelBlock value: model! !
  224.  
  225. Model subclass: #DummyModel
  226.     instanceVariableNames: 'obj coll num text obj2 obj3 '
  227.     classVariableNames: ''
  228.     poolDictionaries: ''
  229.     category: 'Forms-Support'!
  230. DummyModel comment:
  231. 'This is a DummyModel used for the Form class>examples.
  232. It simply has some slots for variables and some messages to change
  233. those varaibels and access them. Note that all the changing methods use
  234. self changed: #varName, where the #varName is that identified in the
  235. instance creation message for the form.
  236. Bernard Horan -- 12 November 1991'!
  237.  
  238.  
  239. !DummyModel methodsFor: 'accessing'!
  240.  
  241. add: anObj
  242.     coll add: anObj.!
  243.  
  244. coll
  245.     ^coll!
  246.  
  247. coll: aColl
  248.     coll := aColl.
  249.     self changed: #coll!
  250.  
  251. num
  252.     ^num!
  253.  
  254. num: aNum
  255.     num := aNum.
  256.     self changed: #num!
  257.  
  258. obj
  259.     ^obj!
  260.  
  261. obj2
  262.     ^obj2!
  263.  
  264. obj2: anObj
  265.     obj2 := anObj.
  266.     self changed: #obj2!
  267.  
  268. obj3
  269.     ^obj3!
  270.  
  271. obj3: anObj
  272.     obj3 := anObj.
  273.     self changed: #obj3!
  274.  
  275. obj: anObj
  276.     obj := anObj.
  277.     self changed: #obj!
  278.  
  279. text
  280.     ^text!
  281.  
  282. text: aText
  283.     text := aText.
  284.     self changed: #text! !
  285.  
  286.  
  287. !DialogView methodsFor: 'construction-adding'!
  288.  
  289. addAll: indexCollection inRows: rowCount fromX: leftFraction toX: rightFraction collect: aBlock
  290.     "Amended by Bernard Horan, Nov 91.
  291.     When this method lays out rows of buttons, they were orignally too
  292.     close. I have changed the magic number to 5 to spread them out a little"
  293.     " Add a set of items to the dialog,
  294.     laid out in a rectangular array.
  295.     aBlock is invoked with successive
  296.     elements of indexCollection,
  297.     and returns an arbitrary VisualPart in a BoundedWrapper."
  298.  
  299.     | size view columnCount prevRow rowX rowY localY localX |
  300.     size := indexCollection size.
  301.     view := CompositePart new.
  302.     columnCount := (size / rowCount) ceiling.
  303.     prevRow := -1.
  304.     localY := yPosition.
  305.     localX := 0.
  306.     (0 to: size - 1) with: indexCollection do:
  307.         [:i :element |
  308.         | buttonWrapper extent  row column |
  309.         row := i // columnCount.
  310.         column := i \\ columnCount.
  311.         row = prevRow ifFalse:
  312.             [prevRow := row.
  313.             rowX := 0.
  314.             rowY := localY].
  315.         buttonWrapper := aBlock value: element.
  316.         extent := buttonWrapper preferredBounds extent.
  317.         rowX := rowX + extent x + 5. "Number change"
  318.         localX := localX max: rowX.
  319.         localY := localY max: rowY + extent y.
  320.         buttonWrapper layout: ((column / columnCount) @ (row / rowCount)
  321.                 extent: (1 / columnCount) @ (1 / rowCount)).
  322.         view addWrapper: buttonWrapper].
  323.     self addWrapper: ((BoundedWrapper on: view) layout: (self layout: (0 @ yPosition corner: localX @ localY) fromX: leftFraction toX: rightFraction))!
  324.  
  325. addTextFieldOn: aModel initially: aString fromX: leftFraction toX: rightFraction
  326.     "Amended by Bernard Horan, Nov 91.
  327.     When this method adds a text-field the field is too short for the
  328.     indented wrapper that the called method uses. The magic number has been
  329.     changed to 5"
  330.     "Create a one-line-high text field on the model."
  331.  
  332.     ^self
  333.         addTextFieldOn: aModel
  334.         initially: aString
  335.         height: TextAttributes default lineGrid + 5 "number change"
  336.         fromX: leftFraction
  337.         toX: rightFraction!
  338.  
  339. addTextFieldOn: aModel initially: aString height: textHeight fromX: leftFraction toX: rightFraction
  340.     "Amended by Bernard Horan, Nov 91.
  341.     I have changed the border that is used to convey the two-and-a-half
  342.     Domensions effect"
  343.     "Create a text field on the model."
  344.  
  345.     | view ctrl border wrapper color |
  346.     view := TextView on: aModel aspect: #value change: #value: menu: nil.
  347.     ctrl := TextItemEditor new.
  348.     view controller: ctrl.
  349.     view editString: aString.
  350.     view selectFrom: 1 to: aString size + 1.
  351.     self controller sendKeyboardTo: ctrl.
  352.     wrapper := BorderedWrapper on: view.
  353.     border := Border new.
  354.     color := self lookPreferences borderColor.
  355.     border setBorderColor: ColorValue white.
  356.     border setBorderWidth: 1.    
  357.     border setTopColor: color.
  358.     border setLeftColor: color. "Border change"
  359.     wrapper border: border; layout: (self layout: (Rectangle origin: 0 @ 0 extent: 0 @ textHeight) fromX: leftFraction toX: rightFraction).
  360.     self addWrapper: wrapper! !
  361.  
  362. DialogView subclass: #FormView
  363.     instanceVariableNames: ''
  364.     classVariableNames: 'CaseDict '
  365.     poolDictionaries: ''
  366.     category: 'Forms'!
  367. FormView comment:
  368. 'I am a refinement of DialogView. I display forms in rows.
  369. My main methods are in the adding and private protocols, where I add
  370. different types of rows depending on the row type.
  371. Class Variables:
  372. CaseDict    <Dictionary> Used instead of a case statement.
  373. Bernard Horan -- 12 November 1991'!
  374.  
  375.  
  376. !FormView methodsFor: 'initialize-release'!
  377.  
  378. release
  379.     model release.
  380.     super release! !
  381.  
  382. !FormView methodsFor: 'adding'!
  383.  
  384. addCycle: aRow 
  385.     "Add a row for a cycle using the row's button image"
  386.     | buttonView yPos left |
  387.     buttonView := LabeledBooleanView new model: ((PluggableAdaptor on: aRow)
  388.                     getBlock: [:m | false]
  389.                     putBlock: [:m :p | m cycle]
  390.                     updateBlock: [:m :p :v | false]).
  391.     buttonView beVisual: aRow buttonImage.
  392.     left := leftIndent.
  393.     yPos := yPosition.
  394.     self addButton: buttonView.
  395.     yPosition := yPos.
  396.     self addCLBVOn: aRow string: aRow maxString.
  397.     leftIndent := left!
  398.  
  399. addFill: aRow
  400.     "Add a row for a text field"
  401.     self addTextFieldOn: aRow initially: aRow value.
  402.     "I have to get the controller somehow..."
  403.     aRow controller: components last component controller!
  404.  
  405. addMenu: aRow 
  406.     "Add a row for a menu using the row's button image"
  407.     | adaptor buttonView yPos left |
  408.     buttonView := LabeledBooleanView new model: (adaptor := PluggableAdaptor on: aRow).
  409.     adaptor
  410.         getBlock: [:m | false]
  411.         putBlock: 
  412.             [:m :p | 
  413.             | point |
  414.             point := (buttonView localPointToGlobal: buttonView bounds bottomLeft)
  415.                         + buttonView topView globalOrigin.
  416.             m menuAt: point]
  417.         updateBlock: [:m :p :v | false].
  418.     buttonView beVisual: aRow buttonImage.
  419.     left :=leftIndent.
  420.     yPos := yPosition.
  421.     self addButton: buttonView.
  422.     yPosition := yPos.
  423.     self addCLBVOn: aRow string: aRow maxString.
  424.     leftIndent := left!
  425.  
  426. addMultiple: aRow
  427.     "add a multiple select row. Each element in the row will appear as
  428.     a checkbox"
  429.     self
  430.         addRow: (1 to: aRow default size)
  431.         fromX: 0
  432.         toX: 1
  433.         collect: 
  434.             [:i | 
  435.             | button |
  436.             button := LabeledBooleanView model: ((PluggableAdaptor on: aRow)
  437.                             collectionIndex: i).
  438.             button beCheckBox.
  439.             button controller beToggle.
  440.             button label: (aRow default at: i).
  441.             BorderedWrapper on: button]!
  442.  
  443. addNumeric: aRow 
  444.     "add a slider"
  445.     | yPos textWidth right view boxHeight wrapper bounds left |
  446.     yPos := yPosition.
  447.     right := rightIndent.
  448.     left := leftIndent.
  449.     textWidth := aRow default last  asFloat printString asComposedText width + 10.
  450.     self rightIndent: leftIndent + textWidth.
  451.     self addCLBVOn: aRow string: aRow default last printString.
  452.     self leftIndent:  leftIndent + textWidth.
  453.     self rightIndent: right.
  454.     boxHeight := yPosition - yPos.
  455.     self yPosition: yPos.
  456.     view := FractionalWidgetView model: aRow.
  457.     view beHorizontal.
  458.     view controller beSlider.
  459.     wrapper := BorderedWrapper on: view.
  460.     wrapper border: self fieldBorder. 
  461.     bounds := wrapper preferredBounds.
  462.     bounds height: (bounds height min: boxHeight).
  463.     bounds width: (bounds width min: self preferredBounds width).
  464.     wrapper layout: (self layout: bounds fromX: 0 toX: 1).
  465.     self addWrapper: wrapper.
  466.     leftIndent := left!
  467.  
  468. addSelect: aRow
  469.     "add a single select row. The elemnets in the row will apear as radio
  470.     buttons"
  471.     self
  472.         addRow: (1 to: aRow default size)
  473.         fromX: 0
  474.         toX: 1
  475.         collect: 
  476.             [:i | 
  477.             | button |
  478.             button := LabeledBooleanView model: ((PluggableAdaptor on: aRow)
  479.                             selectValue: (aRow default at: i)).
  480.             button beRadioButton.
  481.             button controller beSwitch.
  482.             button label: (aRow default at: i).
  483.             BorderedWrapper on: button].! !
  484.  
  485. !FormView methodsFor: 'private'!
  486.  
  487. addButton: buttonView
  488.     "add a button, and set the elft and right indent accordingly"
  489.     | wrapper right buttonWidth |
  490.     buttonView controller beTriggerOnUp.
  491.     wrapper := BorderedWrapper on: buttonView.
  492.     right := rightIndent.
  493.     buttonWidth := wrapper preferredBounds width.
  494.     self rightIndent: leftIndent + buttonWidth.
  495.     self addWrapper: wrapper fromX:0 toX: 1.
  496.     self leftIndent:  leftIndent + buttonWidth.
  497.     self rightIndent: right.!
  498.  
  499. addCLBVOn: aRow string: aString 
  500.     "add a ChangeableLabeledBooleanView (one whose label can be 
  501.     changed) with default label as aString"
  502.  
  503.     | view wrapper |
  504.     view := ChangeableLabeledBooleanView new model: ((LabeledPluggableAdaptor on: aRow)
  505.                     getBlock: [:m | false]
  506.                     putBlock: [:m :p | false]
  507.                     updateBlock: [:m :v :p |  v = #value]
  508.                     labelBlock: [:m | m valueString]).
  509.     view label: aString.
  510.     wrapper := BorderedWrapper on: view.
  511.     wrapper border: (wrapper border copy setBorderColor: self lookPreferences foregroundColor).
  512.     self
  513.         addWrapper: wrapper
  514.         fromX: 0
  515.         toX: 1!
  516.  
  517. addDialogCommands
  518.     "add the commands for the dialog-type view. Modal"
  519.     | ok quit  yp reset |
  520.     ok := LabeledBooleanView new model: ((PluggableAdaptor on: model)
  521.                     getBlock: [:m | true]
  522.                     putBlock: [:m :v | m acceptAndClose]
  523.                     updateBlock: [:m :a :p | false]).
  524.     ok beVisual: ' ok ' asText allBold asComposedText.
  525.     ok controller beTriggerOnUp.
  526.     quit := LabeledBooleanView new model: ((PluggableAdaptor on: model)
  527.                     getBlock: [:m | false]
  528.                     putBlock: [:m :v | m quit]
  529.                     updateBlock: [:m :a :p | false]).
  530.     quit beVisual: 'quit ' asComposedText.
  531.     quit controller beTriggerOnUp.
  532.     reset := LabeledBooleanView new model: ((PluggableAdaptor on: model)
  533.                     getBlock: [:m | false]
  534.                     putBlock: [:m :v | m reset]
  535.                     updateBlock: [:m :a :p | false]).
  536.     reset beVisual: 'reset' asComposedText.
  537.     reset controller beTriggerOnUp.
  538.     ok := (BorderedWrapper on: ok) borderWidth: 2.
  539.     quit := (BorderedWrapper on: quit) inset: 2.
  540.     reset := (BorderedWrapper on: reset) inset: 2.
  541.     yp := self yPosition.
  542.     self indent: 1;
  543.         addWrapper: ok
  544.         fromX: 0.05
  545.         toX: 0.25; 
  546.         yPosition: yp;
  547.         addWrapper: reset
  548.         fromX: 0.4
  549.         toX: 0.6; 
  550.         yPosition: yp;
  551.         addWrapper: quit
  552.         fromX: 0.75
  553.         toX: 0.95;
  554.         addVerticalSpace: 10!
  555.  
  556. addScheduledCommands
  557.     "add the commands for the window-type view. Non-modal"
  558.     | accept quit  yp reset quitAdaptor acceptWrapper quitWrapper resetWrapper |
  559.     accept := LabeledBooleanView new model: ((PluggableAdaptor on: model)
  560.                     getBlock: [:m | true]
  561.                     putBlock: [:m :v | m accept]
  562.                     updateBlock: [:m :a :p | false]).
  563.     accept beVisual: ' accept ' asText allBold asComposedText.
  564.     accept controller beTriggerOnUp.
  565.     quit := LabeledBooleanView new model: (quitAdaptor := PluggableAdaptor on: model).
  566.     quitAdaptor
  567.                     getBlock: [:m | false]
  568.                     putBlock: [:m :v | quit topComponent controller close]
  569.                     updateBlock: [:m :a :p | false].
  570.     quit beVisual: 'quit ' asComposedText.
  571.     quit controller beTriggerOnUp.
  572.     reset := LabeledBooleanView new model: ((PluggableAdaptor on: model)
  573.                     getBlock: [:m | false]
  574.                     putBlock: [:m :v | m reset]
  575.                     updateBlock: [:m :a :p | false]).
  576.     reset beVisual: 'reset' asComposedText.
  577.     reset controller beTriggerOnUp.
  578.     acceptWrapper := (BorderedWrapper on: accept) borderWidth: 2.
  579.     quitWrapper := (BorderedWrapper on: quit) inset: 2.
  580.     resetWrapper := (BorderedWrapper on: reset) inset: 2.
  581.     yp := self yPosition.
  582.     self indent: 1;
  583.         addWrapper: acceptWrapper
  584.         fromX: 0.05
  585.         toX: 0.25; 
  586.         yPosition: yp;
  587.         addWrapper: resetWrapper
  588.         fromX: 0.4
  589.         toX: 0.6; 
  590.         yPosition: yp;
  591.         addWrapper: quitWrapper
  592.         fromX: 0.75
  593.         toX: 0.95;
  594.         addVerticalSpace: 10!
  595.  
  596. addTitle
  597.     "add the title for the view"
  598.     self addVerticalSpace: 5; 
  599.         addTextLabel: model title; 
  600.         addVerticalSpace: 10.!
  601.  
  602. buildDialogViews
  603.     self buildViews.
  604.     self addDialogCommands!
  605.  
  606. buildScheduledViews
  607.     self buildViews.
  608.     self addScheduledCommands!
  609.  
  610. buildViews
  611.     "build the views, and add them to myself"
  612.     | labelLength yPos rows |
  613.     self addTitle.
  614.     labelLength := model rows inject: 0 into: [:max :i | max max: i label width].
  615.     rows := model rows.
  616.     yPos := self yPosition.
  617.     "add the labels"
  618.     self
  619.         addColumn: rows
  620.         fromX: 0
  621.         toX: 1
  622.         collect: 
  623.             [:row | 
  624.             | wrapper |
  625.             wrapper := BorderedWrapper on: row label.
  626.             wrapper border: (wrapper border copy setBorderColor: self lookPreferences backgroundColor).
  627.             wrapper].
  628.     "Then reset the indents and yPosition"
  629.     self yPosition: yPos; leftIndent: leftIndent + labelLength + 5.
  630.     "Now add the rows, using the CaseDict to determine what sort of row to
  631.     add"
  632.     rows do: [:row | self perform: (CaseDict at: row type)
  633.             with: row].!
  634.  
  635. fieldBorder
  636.     "return a border that looks indented into the screen (the opposite
  637.     of a button"
  638.     | border color |
  639.     border := Border new.
  640.     color := self lookPreferences borderColor.
  641.     border setBorderColor: ColorValue white.
  642.     border setBorderWidth: 1.    
  643.     border setTopColor: color.
  644.     border setLeftColor: color.
  645.     ^border! !
  646.  
  647. !FormView methodsFor: 'controller'!
  648.  
  649. defaultController
  650.  
  651.     ^FormController new! !
  652. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  653.  
  654. FormView class
  655.     instanceVariableNames: ''!
  656.  
  657.  
  658. !FormView class methodsFor: 'instance creation'!
  659.  
  660. openDialogViewOn: aForm
  661.     "Build the dialogView and then open it. Modal"
  662.     | view |
  663.     view := self model: aForm.
  664.     view buildDialogViews.
  665.     view open!
  666.  
  667. openScheduledViewOn: aForm 
  668.     "Build the dialogView, then stick it in a ScheduledWindow, so that
  669.     it's not modal.
  670.     The decoration of the window is #dialog, this has no effect
  671.     on the control, it is only used by the windowmanager"
  672.     | view window minSize aRectangle rect |
  673.     view := self model: aForm.
  674.     view buildScheduledViews.
  675.     window := ScheduledWindow new.
  676.     window component: view.
  677.     minSize := view preferredBounds extent.
  678.     window maximumSize: minSize.
  679.     window minimumSize: minSize.
  680.     aRectangle := window defaultRectangle: minSize rounded.
  681.     rect := Screen default makeRectangleVisible: aRectangle.
  682.     window privateOpenIn: rect type: #dialog.
  683.     ScheduledControllers scheduleActiveNoTerminate: window controller.
  684.     window extentEvent: window extent.
  685.     Cursor normal show.
  686.     Processor terminateActive! !
  687.  
  688. !FormView class methodsFor: 'class initialization'!
  689.  
  690. initialize
  691.     "FormView initialize"
  692.     CaseDict := Dictionary new.
  693.     CaseDict at: #fill put: #addFill: ;
  694.             at: #select put: #addSelect: ;
  695.             at: #multiple put: #addMultiple: ;
  696.             at: #number put: #addNumeric: ;
  697.             at: #menu put: #addMenu: ;
  698.             at: #cycle put: #addCycle: .! !
  699.  
  700. DialogCompositeController subclass: #FormController
  701.     instanceVariableNames: ''
  702.     classVariableNames: ''
  703.     poolDictionaries: ''
  704.     category: 'Forms'!
  705. FormController comment:
  706. 'A refinement of DialogCompositeController for use with forms. The only
  707. amendment is in the #processKeyboard method, which recognises ^cr, ^r and ^q
  708. to mean accept and close, reset and quit respectively 
  709. Bernard Horan -- 12 November 1991'!
  710.  
  711.  
  712. !FormController methodsFor: 'keyboard'!
  713.  
  714. processKeyboard
  715.     [self sensor keyboardPressed]
  716.         whileTrue: 
  717.             [| key |
  718.             key := self sensor primKbdPeek.
  719.             key hasCtrl
  720.                 ifTrue: 
  721.                     [| keyValue |
  722.                     self sensor keyboardEvent.
  723.                     keyValue := key keyValue asciiValue.
  724.                     keyValue = 13
  725.                         ifTrue: 
  726.                             ["cr"
  727.                             | c |
  728.                             model acceptAndClose.
  729.                             c := self view topComponent controller.
  730.                             c isNil ifFalse: [c close]].
  731.                     keyValue = 17
  732.                         ifTrue: 
  733.                             ["q"
  734.                             | c |
  735.                             model quit.
  736.                             c := self view topComponent controller.
  737.                             c isNil ifFalse: [c close]].
  738.                     keyValue = 18 ifTrue: ["r"
  739.                         model reset]].
  740.             activeConsumer == nil ifFalse: [activeConsumer processKeyboard]].
  741.     self sensor window flush! !
  742.  
  743. LabeledBooleanView subclass: #ChangeableLabeledBooleanView
  744.     instanceVariableNames: ''
  745.     classVariableNames: ''
  746.     poolDictionaries: ''
  747.     category: 'Forms-Support'!
  748. ChangeableLabeledBooleanView comment:
  749. 'This class is a refinement of LabeledBooleanView. The extra functionality it 
  750. provides is that the label may be changed (hence the class name). This is 
  751. achieved by using a  LabeledPluggableAdaptor (see class comment). The
  752. methods that have been refined are those that refer to the instance variable
  753. ''label'' which is no longer used.  To get the label, the message #label
  754. is sent to the model (the adaptor) which uses a block to ask its model
  755. what the label should be 
  756. Bernard Horan -- 12 November 1991'!
  757.  
  758.  
  759. !ChangeableLabeledBooleanView methodsFor: 'private'!
  760.  
  761. label
  762.     "return the label from the model"
  763.     ^self  model label!
  764.  
  765. useFilling
  766.     " Answer whether to color-invert the image. "
  767.  
  768.     ^self label isEmpty! !
  769.  
  770. !ChangeableLabeledBooleanView methodsFor: 'displaying'!
  771.  
  772. displayOn: aGraphicsContext
  773.     "Display the receiver according to
  774.     the current state of the model."
  775.  
  776.     | extent image startX |
  777.     image := self getImage.
  778.     extent := self bounds extent.
  779.     (self useFilling and: [self isSelected or: [self isInTransition]])
  780.         ifTrue:
  781.             [aGraphicsContext paint: self selectionBackgroundColor.
  782.             aGraphicsContext displayRectangle: self bounds.
  783.             aGraphicsContext paint: self selectionForegroundColor].
  784.     (onImage == nil and: [offImage == nil])
  785.         ifTrue:
  786.             [startX := 4]
  787.         ifFalse:
  788.             [image isNil ifTrue: [image := offImage].  "onImage is nil"
  789.             image
  790.                 displayOn: aGraphicsContext
  791.                 at: 1 @ (extent y - image extent y // 2).
  792.             startX := image extent x + 4]. 
  793.     self label notNil
  794.         ifTrue:
  795.             [| font string |
  796.             font := aGraphicsContext findFont: self getFont.    
  797.             aGraphicsContext font: font.
  798.             string := self label asString.
  799.             self setTextColorOn: aGraphicsContext.
  800.             aGraphicsContext
  801.                 displayString: string
  802.                 at: startX @ (extent y + font ascent - font descent // 2)].
  803.     (self drawBorder and: [self isSelected])
  804.         ifTrue:
  805.             [aGraphicsContext paint: self selectionForegroundColor.
  806.             self container border displayOn: aGraphicsContext forDisplayBox: self bounds]! !
  807.  
  808. !ChangeableLabeledBooleanView methodsFor: 'display box accessing'!
  809.  
  810. preferredBounds
  811.     "Compute the preferredBounds for the receiver"
  812.  
  813.     | imageOff imageOn labelOff labelOn |
  814.     imageOff := offImage isNil ifTrue: [0@0] ifFalse: [offImage preferredBounds extent].
  815.     imageOn := onImage isNil ifTrue: [0@0] ifFalse: [onImage preferredBounds extent].
  816.     labelOff := self label asComposedText preferredBounds extent.
  817.     labelOn := self label asText copy allBold asComposedText preferredBounds extent.
  818.     ^Rectangle
  819.         origin: 0 @ 0
  820.         corner:
  821.             (imageOff x + labelOff x max: imageOn x + labelOn x) + 4 @
  822.             ((imageOff y max: labelOff y) max: (imageOff y max: labelOff y))! !
  823.  
  824. Object subclass: #FormRow
  825.     instanceVariableNames: 'label getMsg updateMsg default value holder '
  826.     classVariableNames: 'Types '
  827.     poolDictionaries: ''
  828.     category: 'Forms'!
  829. FormRow comment:
  830. 'This is an abstract superclass for the ''types'' of rows to be used by a 
  831. Form. 
  832. Instance Variables:
  833. label        <String/Text/ComposedText> The label for the row.
  834. getMsg        <Symbol> the message passed to the model to get the model''s 
  835.         current value
  836. updateMsg    <Symbol> Unfortunately, this is a misnomer, since it should 
  837.         really be putMsg (but I realised too late). This is the
  838.         message passed to the model to set its value (when the form is 
  839.         accepted).
  840. default        <Collection/String> Typically, default will be a collection
  841.         of the labels to be used for the buttons, or items for the
  842.         menu. See the relevant class comment for how that class
  843.         uses default.
  844. value        <Object> This is the object that hangs on to the buffer 
  845.         state of the form (before it is accepted).
  846. holder        <Form> This is the form of which I am a row.
  847.  
  848. Class Variables:
  849. Types        <Dictionary> of symbol->class associations. The symbol is the
  850.         typeName. (All FormRow subclasses respond to the message
  851.         #type with a symbol, eg FillRow responds with #fill).
  852. Bernard Horan -- 12 November 1991'!
  853.  
  854.  
  855. !FormRow methodsFor: 'initialize-release'!
  856.  
  857. label: aLabel getMsg: aGetMsg updateMsg: anUpdateMsg 
  858.     label := aLabel asComposedText.
  859.     getMsg := aGetMsg.
  860.     updateMsg := anUpdateMsg!
  861.  
  862. release
  863.  
  864.     holder := nil.
  865.     super release! !
  866.  
  867. !FormRow methodsFor: 'accessing'!
  868.  
  869. default
  870.     ^default!
  871.  
  872. default: aDefault
  873.     "aDefault should be a collection/string"
  874.     default := aDefault.!
  875.  
  876. holder: aHolder
  877.     holder := aHolder!
  878.  
  879. label
  880.     ^label!
  881.  
  882. maxString
  883.     "return the largest string (in pixels) from the defaults"
  884.  
  885.     | cText |
  886.     cText := String new asComposedText.
  887.     1 to: default size do: 
  888.         [:i | 
  889.         | iText |
  890.         iText := (default at: i) asComposedText.
  891.         iText width > cText width ifTrue: [cText := iText]].
  892.     ^cText string!
  893.  
  894. model
  895.     ^holder model!
  896.  
  897. type
  898.     ^self class type! !
  899.  
  900. !FormRow methodsFor: 'pluggability'!
  901.  
  902. value
  903.     ^self basicValue!
  904.  
  905. value: newValue 
  906.     self basicValue: newValue! !
  907.  
  908. !FormRow methodsFor: 'updating'!
  909.  
  910. update: aSymbol
  911.     aSymbol = getMsg ifTrue:[self open]! !
  912.  
  913. !FormRow methodsFor: 'private'!
  914.  
  915. accept
  916.     self model perform: updateMsg with: self basicValue!
  917.  
  918. basicValue
  919.     ^value!
  920.  
  921. basicValue: newValue 
  922.     value := newValue.
  923.     self changed: #value!
  924.  
  925. open
  926.     "get the current value"
  927.     self basicValue: (self model perform: getMsg) copy! !
  928. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  929.  
  930. FormRow class
  931.     instanceVariableNames: ''!
  932.  
  933.  
  934. !FormRow class methodsFor: 'class initialization'!
  935.  
  936. initialize
  937.     "FormRow initialize"
  938.     Types := Dictionary new.
  939.     self subclasses do: [:i | Types at: i type put: i]! !
  940.  
  941. !FormRow class methodsFor: 'instance creation'!
  942.  
  943. label: aLabel getMsg: aGetMsg updateMsg: anUpdateMsg
  944.     ^self new label: aLabel getMsg: aGetMsg updateMsg: anUpdateMsg!
  945.  
  946. label: aLabel type: aType getMsg: aGetMsg updateMsg: anUpdateMsg default: default
  947.     ^((Types at: aType) label: aLabel getMsg: aGetMsg updateMsg: anUpdateMsg) default: default! !
  948.  
  949. FormRow subclass: #NumericRow
  950.     instanceVariableNames: ''
  951.     classVariableNames: ''
  952.     poolDictionaries: ''
  953.     category: 'Forms'!
  954. NumericRow comment:
  955. 'I am a row which presents the user with a slider for entering a number.
  956. Instance variables:
  957. default (from super) <Collection> I use the collection to determine the
  958.             interval that the number may lie in. The collection
  959.             should have three elements: the start, the stop and
  960.             the increment for the Interval. I actually keep the
  961.             interval in the instance variable.
  962. Ny type is #number
  963. Bernard Horan -- 12 November 1991'!
  964.  
  965.  
  966. !NumericRow methodsFor: 'private'!
  967.  
  968. diff
  969.     "the difference between first and last"
  970.     ^default last - default first!
  971.  
  972. open
  973.     super open.
  974.     self basicValue isNil ifTrue:[self value: 1/2]! !
  975.  
  976. !NumericRow methodsFor: 'pluggability'!
  977.  
  978. value
  979.     ^super value/ self diff!
  980.  
  981. value: aFraction 
  982.     "set the value from a fraction"
  983.  
  984.     | val step |
  985.     val := aFraction * self diff.
  986.     step := default increment.
  987.     super value: val // step * step!
  988.  
  989. valueString
  990.     "return the value as a String"
  991.     ^self basicValue printString! !
  992.  
  993. !NumericRow methodsFor: 'accessing'!
  994.  
  995. default: aDefault 
  996.     "aDefault should be a 3 element collection, from which an interval 
  997.     can be created"
  998.  
  999.     default := aDefault first to: (aDefault at: 2)
  1000.                 by: aDefault last! !
  1001. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1002.  
  1003. NumericRow class
  1004.     instanceVariableNames: ''!
  1005.  
  1006.  
  1007. !NumericRow class methodsFor: 'utilities'!
  1008.  
  1009. type
  1010.     ^#number! !
  1011.  
  1012. FormRow subclass: #FillRow
  1013.     instanceVariableNames: 'textController '
  1014.     classVariableNames: ''
  1015.     poolDictionaries: ''
  1016.     category: 'Forms'!
  1017. FillRow comment:
  1018. 'I am a row allowing the user to enter a line of text. 
  1019. Instance variables:
  1020. default (from super)     <String> the default string to be used in the line
  1021.             of text. If the model supplies a string value, then
  1022.             the value will take precedence over the default.
  1023. textController    <TextItemEditor> I have to hang on to the controller to force
  1024.         it to accept when the user has entered text. This is a nasty 
  1025.         hack, but I can''t see any way around it.
  1026. My type is #fill.
  1027. Bernard Horan -- 12 November 1991'!
  1028.  
  1029.  
  1030. !FillRow methodsFor: 'private'!
  1031.  
  1032. accept
  1033.     "Force my controller to accept the text into my value,
  1034.     then do what my super does"
  1035.     textController accept.
  1036.     super accept!
  1037.  
  1038. open
  1039.     | string |
  1040.     super open.
  1041.     (self value isNil or:[self value isEmpty]) ifTrue:[string := default] ifFalse:[string := self value].
  1042.     self value: string! !
  1043.  
  1044. !FillRow methodsFor: 'controller accessing'!
  1045.  
  1046. controller: aController
  1047.     textController := aController! !
  1048.  
  1049. !FillRow methodsFor: 'initialize-release'!
  1050.  
  1051. release
  1052.     textController release.
  1053.     super release! !
  1054. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1055.  
  1056. FillRow class
  1057.     instanceVariableNames: ''!
  1058.  
  1059.  
  1060. !FillRow class methodsFor: 'utilities'!
  1061.  
  1062. type
  1063.     ^#fill! !
  1064.  
  1065. FormRow subclass: #CycleRow
  1066.     instanceVariableNames: 'index '
  1067.     classVariableNames: 'CycleImage '
  1068.     poolDictionaries: ''
  1069.     category: 'Forms'!
  1070. CycleRow comment:
  1071. 'I am a cycling row. That means that I only show one item at a time, and the
  1072. user must hit a button to get to the next option.
  1073. Instance Variables:
  1074. default (from super)    A collection of items, preferably strings.
  1075. index    <Integer> The integer is used to hang on to the current item being
  1076.     displayed. It is incremented by one each time the user cycles the row.
  1077.     When the row is opened, if there is no value in the model, the index is
  1078.     set to 0, otherwise it is set to the index of the value in the 
  1079.     collection of defaults -1.
  1080. My type is #cycle.
  1081. Class Variables:
  1082. CycleImage    <CachedImage> representing the Cycle button.
  1083. Bernard Horan -- 12 November 1991'!
  1084.  
  1085.  
  1086. !CycleRow methodsFor: 'pluggability'!
  1087.  
  1088. valueString
  1089.     ^self value asString! !
  1090.  
  1091. !CycleRow methodsFor: 'accessing'!
  1092.  
  1093. buttonImage
  1094.     ^CycleImage!
  1095.  
  1096. cycle
  1097.     index := index + 1 \\ default size.
  1098.     self value: (default at: index + 1)! !
  1099.  
  1100. !CycleRow methodsFor: 'private'!
  1101.  
  1102. open
  1103.     super open.
  1104.     self value isNil
  1105.         ifTrue: 
  1106.             [index := 0.
  1107.             self value: default first]
  1108.         ifFalse: [index := (default indexOf: value)
  1109.                         - 1]! !
  1110. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1111.  
  1112. CycleRow class
  1113.     instanceVariableNames: ''!
  1114.  
  1115.  
  1116. !CycleRow class methodsFor: 'utilities'!
  1117.  
  1118. type
  1119.     ^#cycle! !
  1120.  
  1121. !CycleRow class methodsFor: 'class initialization'!
  1122.  
  1123. initialize
  1124.     "CycleRow initialize"
  1125.     CycleImage :=  CachedImage on: (Image
  1126.         extent: 16@16
  1127.         depth: 1
  1128.         palette: CoveragePalette monoMaskPalette
  1129.         bits: #[16r07 16rC0 16r0F 16rE0 16r18 16r34 16r30 16r1C 16r60 16r1C 16r20 16r3C 16r00 16r00 16r00 16r00 16r78 16r08 16r70 16r0C 16r70 16r18 16r58 16r30 16r0F 16rE0 16r07 16rC0 16r00 16r00 16r00 16r00] pad: 16)! !
  1130.  
  1131. FormRow subclass: #MenuRow
  1132.     instanceVariableNames: ''
  1133.     classVariableNames: 'MenuImage '
  1134.     poolDictionaries: ''
  1135.     category: 'Forms'!
  1136. MenuRow comment:
  1137. 'I am a row which offers the users a menu of options from which to choose. The
  1138. menu appears below a button which the user must press. When the user selects
  1139. an item from the menu, the view alongside the button is updated to display
  1140. that item.
  1141. Default is used as a collection of items for the menu.
  1142. Class Variables:
  1143. MenuImage    <CachedImage> for the button.
  1144. My type is #menu
  1145. Bernard Horan -- 12 November 1991'!
  1146.  
  1147.  
  1148. !MenuRow methodsFor: 'accessing'!
  1149.  
  1150. buttonImage
  1151.     ^MenuImage!
  1152.  
  1153. menuAt: aPoint
  1154.     | menu val |
  1155.     menu := PopUpMenu labelList: (Array with: default) values: default.
  1156.     val := menu startUpAt: aPoint.
  1157.     val = 0 ifFalse:[self value: val]! !
  1158.  
  1159. !MenuRow methodsFor: 'pluggability'!
  1160.  
  1161. valueString
  1162.     ^self value asString! !
  1163.  
  1164. !MenuRow methodsFor: 'private'!
  1165.  
  1166. open
  1167.     super open.
  1168.     self value isNil ifTrue:[self value: default first]! !
  1169. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1170.  
  1171. MenuRow class
  1172.     instanceVariableNames: ''!
  1173.  
  1174.  
  1175. !MenuRow class methodsFor: 'utilities'!
  1176.  
  1177. type
  1178.     ^#menu! !
  1179.  
  1180. !MenuRow class methodsFor: 'class initialization'!
  1181.  
  1182. initialize
  1183.     "MenuRow initialize"
  1184.     MenuImage :=  CachedImage on: (Image
  1185.         extent: 16@16
  1186.         depth: 1
  1187.         palette: CoveragePalette monoMaskPalette
  1188.         bits: #[16r7F 16rFF 16r3F 16rFE 16r3F 16rFE 16r1F 16rFC 16r0F 16rF8 16r0F 16rF8 16r07 16rF0 16r07 16rF0 16r03 16rE0 16r01 16rC0 16r01 16rC0 16r00 16r80 16r00 16r00 16r00 16r00 16r00 16r00 16r00 16r00] pad: 16)! !
  1189.  
  1190. FormRow subclass: #SingleSelectRow
  1191.     instanceVariableNames: ''
  1192.     classVariableNames: ''
  1193.     poolDictionaries: ''
  1194.     category: 'Forms'!
  1195. SingleSelectRow comment:
  1196. 'I am a row from which the user can select only one element. (I can however 
  1197. start my life with zero elements selected). I use the default iv as a 
  1198. collection of labels for the buttons.
  1199. My type is #select.
  1200. Bernard Horan -- 12 November 1991'!
  1201.  
  1202. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1203.  
  1204. SingleSelectRow class
  1205.     instanceVariableNames: ''!
  1206.  
  1207.  
  1208. !SingleSelectRow class methodsFor: 'utilities'!
  1209.  
  1210. type
  1211.     ^#select! !
  1212.  
  1213. FormRow subclass: #MultipleSelectRow
  1214.     instanceVariableNames: ''
  1215.     classVariableNames: ''
  1216.     poolDictionaries: ''
  1217.     category: 'Forms'!
  1218. MultipleSelectRow comment:
  1219. 'I am a multiple select row, i.e. pick n from m where 0<=n<=m. I use the
  1220. default iv as a collection of labels for the buttons. My type is #fill.
  1221. Bernard Horan -- 12 November 1991'!
  1222.  
  1223.  
  1224. !MultipleSelectRow methodsFor: 'pluggability'!
  1225.  
  1226. at: i
  1227.     ^self value includes: (default at: i)!
  1228.  
  1229. at: i put: anObject 
  1230.     | obj |
  1231.     obj := default at: i.
  1232.     (self value includes: obj)
  1233.         ifTrue: [self value remove: obj]
  1234.         ifFalse: [self value add: obj].
  1235.     self changed: #value! !
  1236. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1237.  
  1238. MultipleSelectRow class
  1239.     instanceVariableNames: ''!
  1240.  
  1241.  
  1242. !MultipleSelectRow class methodsFor: 'utilities'!
  1243.  
  1244. type
  1245.     ^#multiple! !
  1246.  
  1247. MenuRow initialize!
  1248. FormRow initialize!
  1249. CycleRow initialize!
  1250. FormView initialize!
  1251.  
  1252.  
  1253.  
  1254.